package org.gordonframework.core.tween {
	import org.gordonframework.core.events.GProcessEvent;
	import org.gordonframework.core.process.GProcess;
	import org.gordonframework.core.tween.interpolate.GObjectInterpolator;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;	

	/**
	 * @author sampet
	 */
	public class GTween extends EventDispatcher implements GProcess {

		private static var sprite:Sprite;
		private static var targets:Dictionary = new Dictionary( );
		private static var time:int;

		private var _progress:Number = 0;
		private var _running:Boolean = false;
		private var _complete:Boolean = false;

		private var target:Object;
		private var values:Object;
		private var property:GObjectInterpolator;
		private var startTime:Number;
		private var duration:Number;
		private var delay:Number;
		private var ease:Function;
		private var initializeAfterDelay:Boolean;

		
		/**
		 * Creates an instance of the GTween class.
		 * 
		 * @param target The Object to tween. 
		 * @param values An Object with the properties to tween.
		 * @param duration The duration of the tween in seconds.
		 * @param delay 
		 * @param ease The ease calculation.
		 * @param initializeAfterDelay  
		 */
		public function GTween(target:Object, values:Object, duration:Number = 0, delay:Number = 0, ease:Function = null, initializeAfterDelay:Boolean = false) {
			this.target = target;
			this.values = values;
			this.duration = duration * 1000;
			this.delay = delay * 1000;
			this.ease = ease == null ? easeNone : ease;
			this.initializeAfterDelay = initializeAfterDelay;
		}

		/**
		 * The current progression of the tween.
		 */
		public function get progress():Number {
			return _progress;
		}

		/**
		 * Indicates whether or not the tween is running.
		 */
		public function get running():Boolean {
			return _running;
		}

		/**
		 * Indicates whether or not the tween is complete.
		 */
		public function get complete():Boolean {
			return _complete;
		}

		/**
		 * Starts the tween.
		 */
		public function start():void {
			if(!sprite) {
				time = getTimer( );
				sprite = new Sprite( );	
				sprite.addEventListener( Event.ENTER_FRAME, enterFrameHandler );
			}

			property = null;
			startTime = time;
			_progress = 0;
			_running = true;
			_complete = false;
			
			if(!initializeAfterDelay) {
				initializeProperty( );	
			}

			if(!targets[target]) {
				targets[target] = [];
			}
			
			targets[target].push( this );
			
			update( );
		}

		/**
		 * Stops the tween if its running.
		 */			
		public function stop():void {
			if(_running) {
				_progress = 0;
				_running = false;
				_complete = false;
				if(hasEventListener( GProcessEvent.STOP )) {
					dispatchEvent( new GProcessEvent( GProcessEvent.STOP ) );				
				}				
			}
		}

		private function initializeProperty():void {
			property = new GObjectInterpolator( target, values );
			for each(var tween:GTween in targets[target]) {
				if(tween != this && tween.property) {
					if(tween.property.removeProperties( property ) == 0) {
						tween.stop( );
					}
				}
			}
		}

		private function update():void {
			var position:Number = time - startTime - delay;
			if(position >= 0) {
				if(!property) {
					if(initializeAfterDelay) {
						initializeProperty( );	
					}					
					if(hasEventListener( GProcessEvent.START )) {
						dispatchEvent( new GProcessEvent( GProcessEvent.START ) );				
					}
				}
				if(position < duration) {
					property.interpolate( target, _progress = ease( position, 0, 1, duration ) );
					if(hasEventListener( GProcessEvent.PROGRESS )) {
						dispatchEvent( new GProcessEvent( GProcessEvent.PROGRESS, false, false, _progress ) );				
					}
				} else {
					property.interpolate( target, _progress = 1 );
					if(hasEventListener( GProcessEvent.PROGRESS )) {
						dispatchEvent( new GProcessEvent( GProcessEvent.PROGRESS, false, false, 1 ) );				
					}
					_running = false;
					_complete = true;
					if(hasEventListener( GProcessEvent.COMPLETE )) {
						dispatchEvent( new GProcessEvent( GProcessEvent.COMPLETE, false, false, 1 ) );				
					}
				}
			}
		}

		public static function easeNone(t:Number, b:Number, c:Number, d:Number):Number {
			return c * t / d + b;
		}

		public static function easeIn(t:Number, b:Number, c:Number, d:Number):Number {
			return c * (t /= d) * t * t * t * t + b;
		}		

		public static function easeOut(t:Number, b:Number, c:Number, d:Number):Number {
			return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
		}

		public static function easeInOut(t:Number, b:Number, c:Number, d:Number):Number {
			if ((t /= d / 2) < 1) {
				return c / 2 * t * t * t * t * t + b;
			}
			return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
		}

		private static function enterFrameHandler(event:Event):void {
			time = getTimer( );
			var tweens:Array;
			var tween:GTween;
			var i:uint;
			for each(tweens in targets) {
				i = tweens.length;
				while(i-- > 0) {
					tween = tweens[i];
					if(!tween.running) {
						tweens.splice( i, 1 );
						if(tweens.length == 0) {
							targets[tween.target] = null;
							delete targets[tween.target];
						}
					} else {
						tween.update( );
					}
				}
			}
		}		
	}
}
